package com.teaphy.demo;

import com.teaphy.annotations.Author;
import com.teaphy.annotations.Explain;
import com.teaphy.annotations.NotNull;
import com.teaphy.data.GradeType;
import com.teaphy.data.Person;
import com.teaphy.data.Student;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.Arrays;

/**
 * 获取Class的三种方式
 */
public class ClassDemo {
	
	@SuppressWarnings("unchecked")
	public static void main(String[] args) {
		
		// 1. 获取Class实例
		// 1.1 Object类中的`getClass()`方法装返回一个Class类型的实例
		Student student = new Student(2);
		Class clStudent = student.getClass();
		
		// 1.2 调用 `Class`静态方法`forName`获得类名对应的Class对象
		// 只有在className是类名或接口名时才能够执行。
		// 否则，`forName`方法将抛出一个`checkedException(已检查异常)`。
		// 无论何时使用这个方法，都应该提供一个异常处理器。
		try {
			String className = "com.teaphy.data.Student";
			Class clStudent_1 = Class.forName(className);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		
		// 1.3 如果T是任意的Java类型，`T.class`将代表匹配的类对象。
		Class<Student> clStudent_2 = Student.class;
		
		// 可以利用==运算符实现两个类型对象比较的操作
		System.out.println(clStudent == clStudent_2);
		
		// 2. 创建类的实例
		try {
			// 2.1 将`getClass`&`newInstance`配合一起使用，可以动态地根据配置的字符串的类名创建一个对象。
			System.out.println("创建类的实例 --> Start");
			Student student1 = student.getClass().newInstance();
			System.out.println("End --> 创建类的实例");
		} catch (InstantiationException | IllegalAccessException e) {
			e.printStackTrace();
		}
		
		// 2.2 将`forName`&`newInstance`配合一起使用，可以动态地根据配置的字符串的类名创建一个对象。
		String clName = "com.teaphy.data.Student";
		try {
			System.out.println("`forName`&`newInstance` --> Start");
			Object student2 = Class.forName(clName).newInstance();
			if (student2 instanceof Student) {
				System.out.println("这是一个Student的实例");
				
				System.out.println(student2.toString());
			} else {
				System.out.println("这不是一个Student的实例");
			}
			System.out.println("End --> `forName`&`newInstance`");
		} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
			e.printStackTrace();
		}
		
		// 3. 由此对象表示的类或接口直接实现的接口的Class/Type
		System.out.println("直接实现的接口的Class --> Start");
		Class[] interfaces = clStudent.getInterfaces();
		Class clInterface;
		for (int i = 0; i < interfaces.length; i++) {
			clInterface = interfaces[i];
			System.out.println("Student实现的接口的Class - " + (i + 1) + " : " + clInterface.getName());
		}
		System.out.println("End --> 直接实现的接口的Class");
		
		System.out.println("直接实现的接口的Type --> Start");
		
		Type[] interfaceTypes = clStudent.getGenericInterfaces();
		Type interfaceType;
		
		for (int i = 0; i < interfaceTypes.length; i++) {
			interfaceType = interfaceTypes[i];
			System.out
				.println("Student直接实现的接口 的Type- " + (i + 1) + " : " + interfaceType.getTypeName());
		}
		
		System.out.println("End --> 直接实现的接口的End");
		
		// 4. 此Class表示的实体（类，接口，基本类型或void）的直接超类的Type。
		// Student继承的父类
		Type clSupperStu = clStudent.getGenericSuperclass();
		System.out.println("Student继承的父类- " + clSupperStu.getTypeName());
		
		// Person继承的父类
		System.out.println("Person继承的父类- " + Person.class.getGenericSuperclass().getTypeName());
		
		// 5. 注解
		// 5.1 返回该Class的注解列表。
		Annotation[] annotations = clStudent.getAnnotations();
		System.out.println("Student的注解列表：" + Arrays.toString(annotations));
		// 7.2 ：如果该类被指定的类注解，返回该注解。若未注解，返回null。
		Annotation aNotNull = clStudent.getAnnotation(NotNull.class);
		if (null == aNotNull) {
			System.out.println("Student没有被NotNull注解");
		} else {
			System.out.println("Student被NotNull注解");
		}
		
		Annotation aExplain = clStudent.getAnnotation(Explain.class);
		if (null == aExplain) {
			System.out.println("Student没有被Explain注解");
		} else {
			System.out.println("Student被Explain注解, 其value为：" + aExplain.toString());
		}
		
		// 7.3 返回某个类型的重复注解
		Annotation[] aAuthor = clStudent.getAnnotationsByType(Author.class);
		System.out.println("返回Author.class的重复注解数组: " + Arrays.toString(aAuthor));
		
		// 6. 获取内部类
		// 6.1 getClasses()返回一个Class对象数组，这些对象表示在此Class对象内所声明的所有
		// 公共的(public修饰的)内部类/接口,以及从父类、父接口那里继承来的内部类/接口。
		Class[] clsInteriorPub = clStudent.getClasses();
		System.out.println("当前类及父类的所有公共的内部类：" + Arrays.toString(clsInteriorPub));
		// 6.2 getDeclaredClasses()返回一个Class对象数组，这些对象表示在此Class对象内所声明的
		// 所有(public、private、protected、default)内部类/接口
		Class[] clsInteriorAll = clStudent.getDeclaredClasses();
		System.out.println("当前类中声明的所有内部类：" + Arrays.toString(clsInteriorAll));
	
		
		// 7. 获取数组的元素类型Class,前提是该Class对象表示数组。
		// 如果该Class对象不表示数组将返回null
		Student[] students = new Student[9];
		Class clComponent = students.getClass().getComponentType();
		System.out.println("数组的元素类型：" + clComponent.getName());
		System.out.println("Class对象不表示数组将返回：" + clStudent.getComponentType());
		
		// 8 封闭类
		// 1. getEnclosingClass()->Class<?>:该类是在那个类中定义的， 比如直接定义的内部类或匿名内部类
		// 2. getEnclosingConstructor()->Constructor<?>:该类是在哪个构造函数中定义的，比如构造方法中定义的匿名内部类
		// 3. getEnclosingMethod()->Method:该类是在哪个方法中定义的，比如方法中定义的匿名内部类
		// Demo见 EnclosingClassDemo
		
		// 9. getEnumConstants()-> 返回此枚举类的元素列表，如果此Class对象不表示枚举类型，则返回null
		Class clGradeType = GradeType.class;
		checkEnumClass(clGradeType);
		
		Class clEnumStudent = Student.class;
		checkEnumClass(clEnumStudent);
		
		// 10. 修饰符
		int modify = clStudent.getModifiers();
		String modifier = Modifier.toString(modify);
		System.out.println("Student的修饰符：" + modifier);
		
		// 11. 名称
		// 11.1 getName()：获取Class的名称，包括 包名和类名
		System.out.println("getName()：" + clStudent.getName());
		// 11.2 getSimpleName()：获取Class的类名
		System.out.println("getSimpleName()：" + clStudent.getSimpleName());
		// 11.3 getTypeName()：与 getName()一样，获取Class的名称，包括 包名和类名
		System.out.println("getTypeName()：" + clStudent.getTypeName());
		// 11.4 toGenericString()：返回描述此Class的字符串，包括有关修饰符、类型参数、包名和类名
		System.out.println("toGenericString()：" + clStudent.toGenericString());
		// 11.5 toString(): 将对象转换为字符串,包括类型参数、包名和类名
		System.out.println("toString()：" + clStudent.toString());
		
		// 12 其他
		System.out.println("getCanonicalName()：" + clStudent.getCanonicalName());
		System.out.println("getClassLoader()：" + clStudent.getClassLoader());
		System.out.println("getPackage()：" + clStudent.getPackage());
		System.out.println("getProtectionDomain()：" + clStudent.getProtectionDomain());
		System.out.println("getSigners()：" + Arrays.toString(clStudent.getSigners()));

		//  13. 构造函数
		// 13.1 getConstructors(): 返回一个Constructor数组,其元素为Class对象所表示的类的所有`public`构造函数。
		Constructor[] constructors = clStudent.getConstructors();
		System.out.println("getConstructors(): " + Arrays.toString(constructors));
		// 13.2 getConstructor(Class<?>... parameterTypes)：返回一个Constructor对象，该对象表示此Class对象
		// 所表示的类或接口的指定定参数类型的`public`构造函数。
		// 如果找不到指定参数类型的构造函数，将抛出`NoSuchMethodException`异常。
		try {
			Constructor constructorByType = clStudent.getConstructor(int.class, String.class);
			System.out.println(constructorByType);
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
			System.out.println("未找到指定参数类型的构造函数");
		}
		
		try {
			Constructor constructorByType = clStudent.getConstructor(String.class, int.class, String.class);
			System.out.println(constructorByType);
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
			System.out.println("未找到指定参数类型的构造函数");
		}
		// 13.3 getDeclaredConstructors(): 返回Constructor数组，其元素为此Class对象表示的类声明的所有构造
		// 函数，包括`public`，`protected`，`default`(包)访问和`private`。
		Constructor[] declaredConstructors = clStudent.getDeclaredConstructors();
		System.out.println("getDeclaredConstructors(): " + Arrays.toString(constructors));
		// 13.4getDeclaredConstructor(Class<?>... parameterTypes)：返回一个Constructor对象，该对象表示此Class对象
		// 所表示的类或接口的指定定参数类型的构造函数,包括`public`，`protected`，`default`(包)和`private`。
		// 如果找不到指定参数类型的构造函数，将抛出`NoSuchMethodException`异常。
		try {
			Constructor constructorByType = clStudent.getDeclaredConstructor(String.class, int.class, String.class);
			System.out.println(constructorByType);
		} catch (NoSuchMethodException e) {
//			e.printStackTrace();
			System.out.println("未找到指定参数类型的构造函数");
		}
		
		// 14. 字段
		// 14.1 getFields()：返回一个Field数组，其元素为Class所表示的类或接口及其父类的的所有`public`字段。
		Field[] fields = clStudent.getFields();
		System.out.println("getFields(): " + Arrays.toString(fields));
		// 14.2 getField(String)：返回一个Field对象，该对象表示Class对象所表示的类或接口及其父类的指定名称的`public`字段。
		// 如果找不到指定名称的字段，将抛出`NoSuchFieldException`异常。
		try {
			Field field1 = clStudent.getField("sex");
			Field field2 = clStudent.getField("name");
			System.out.println("getField(\"sex\"): " + field1);
			System.out.println("getField(\"name\"): " + field2);
		} catch (NoSuchFieldException e) {
//			e.printStackTrace();
			System.out.println("未找到指定名称的字段");
		}
		
		try {
			clStudent.getField("none");
		} catch (NoSuchFieldException e) {
//			e.printStackTrace();
			System.out.println("未找到指定名称的字段");
		}
		
		// 14.3 getDeclaredFields()：返回Field数组，其元素为Class对象表示的类或接口声明的所有字段，
		// 包括`public`，`protected`，`default`和`private`，但是不包括父类中声明的字段。
		Field[] declaredFields = clStudent.getDeclaredFields();
		System.out.println("getDeclaredFields(): " + Arrays.toString(declaredFields));
		// 14.4 getDeclaredField(String)：返回一个Field对象，该对象表示Class对象所表示的类或接口的声明的指定
		// 名称字段，包括`public`，`protected`，`default`和`private`，但是不包括父类中声明的字段。、
		// 如果找不到指定名称的字段，将抛出`NoSuchFieldException`异常。
		try {
			Field declaredField = clStudent.getDeclaredField("sex");
			System.out.println("getDeclaredField(\"sex\")" + declaredField);
		} catch (NoSuchFieldException e) {
			//			e.printStackTrace();
			System.out.println("未找到指定名称的字段");
		}
		
		try {
			Field declaredField = clStudent.getDeclaredField("name");
			System.out.println("getDeclaredField(\"name\")" + declaredField);
		} catch (NoSuchFieldException e) {
			//			e.printStackTrace();
			System.out.println("未找到指定名称的字段");
		}
		
		// 15. 函数
		// 15.1 getMethods()：返回一个Method数组，其元素表示此Class对象所表示的类或接口的所有公共方法，
		// 包括由类或接口声明的以及从超类和超接口继承的。
		Method[] methods = clStudent.getMethods();
		System.out.println("getMethods()：" + Arrays.toString(methods));
		
		// 15.2 getMethod(String)：返回一个Method对象，该对象表示此Class对象所表示的类或接口的指定签名的`public`方法。
		// 包括由类或接口声明的以及从超类和超接口继承的。
		// 如果找不到指定签名的函数，将抛出`NoSuchMethodException`异常。
		try {
			Method method1 = clStudent.getMethod("doHomeWork", String.class);
			Method method2 = clStudent.getMethod("getId");
			System.out.println("getMethod(\"doHomeWork\")：" + method1);
			System.out.println("getMethod(\"getId\")：" + method2);
		} catch (NoSuchMethodException e) {
//			e.printStackTrace();
			System.out.println("未找到指定签名的函数");
		}
		
		try {
			Method method = clStudent.getMethod("noneMethod", String.class);
			System.out.println("getMethod(\"noneMethod\")：" + method);
		} catch (NoSuchMethodException e) {
//			e.printStackTrace();
			System.out.println("未找到指定签名的函数");
		}
		
		// 15.3 getDeclaredMethods()：返回一个Method数组，其元素表示Class对象所表示的类或接口的所有声明方法，
		// 包括`public`，`protected`，`default`和`private`，接口的实现方法包括在内，但不包括继承(`override`)和父类声明的方法。
		Method[] declaredMethods = clStudent.getDeclaredMethods();
		System.out.println("getDeclaredMethod()：" + Arrays.toString(declaredMethods));
		
		// 15.4 getDeclaredMethod(String)：返回一个Method对象，其元素表示Class对象所表示的类或接口的指定签名的方法，
		// 包括`public`，`protected`，`default`和`private`，接口的实现方法包括在内，但不包括继承(`override`)和父类声明的方法。
		// 如果找不到指定签名的函数，将抛出`NoSuchMethodException`异常。
		try {
			Method method1 = clStudent.getDeclaredMethod("doHomeWork", String.class);
			System.out.println("getDeclaredMethod(\"doHomeWork\")：" + method1);
		} catch (NoSuchMethodException e) {
//			e.printStackTrace();
			System.out.println("未找到指定签名的函数");
		}
		
		try {
			Method method = clStudent.getDeclaredMethod("getId");
			System.out.println("getDeclaredMethod(\"getId\")：" + method);
		} catch (NoSuchMethodException e) {
//			e.printStackTrace();
			System.out.println("未找到指定签名的函数");
		}
		
		// 16 转换对象
		// 16.1 asSubclass(Class)：将调用这个方法的class对象转换成由clazz参数所表示的class对象的某个子类。
		// 可以通过它抛出异常的方式来判断一个类是否是另外一个类的子类
		try {
			// 1. clz的类型为 Class<?>
			String clzName = "com.teaphy.data.Student";
			Class<?> clz = Class.forName(clzName);
			// 2. clzSub的类型为Class<? extends Person>，实际上是clz的类型范围的缩减
			// 当"com.teaphy.data.Student"是Person的子类时，正常执行;
			// 当不是Person的子类时，抛出ClassCastException，这时可以做些别的处理；
			Class<? extends Person> clzSub =clz.asSubclass(Person.class);
			System.out.println(clName + "是Person的子类");
		} catch (ClassNotFoundException e) {
//			e.printStackTrace();
			System.out.println("com.teaphy.Student不是Person的子类");
		}
		
		// 16.2 cast(Object)：将一个对象装换为类或者接口，此方法只能转换当前类型或其子类下的对象，只是简单进行强转。
		Person personCast = new Person();
		Student studentCast = new Student();
		
		System.out.println("personCast.getClass().getName()" + personCast.getClass().getName());
		System.out.println("studentCast.getClass().getName()" + studentCast.getClass().getName());
		
		// 将personCast的类型由Person强制转换为Student
		personCast = Student.class.cast(studentCast);
		System.out.println("Student.class.cast(personCast): " + personCast.getClass().getName());
		
		// 11. 获取泛型
		// 在泛型代码内部，无法获得任何有关泛型参数类型的信息，因为泛型的插除。
		// 1. 如果是继承基类而来的泛型，就用 getGenericSuperclass() , 转型为 ParameterizedType 来获得实际类型
		// 2. 如果是实现接口而来的泛型，就用 getGenericInterfaces() , 针对其中的元素转型为 ParameterizedType 来获得实际类型
		// 3. 我们所说的 Java 泛型在字节码中会被擦除，并不总是擦除为 Object 类型，而是擦除到上限类型

//		// AnnotatedType
//		// 8.1 这些对象表示使用类型来指定此Class对象所表示的实体的超接口
//		// java8提供AnnotatedType接口，该接口用来代表被注解修饰的类型。
//		// 该接口继承AnnotatedElement接口。同时多了一个public Type getType()方法，用于返回注解修饰的类型。
//		AnnotatedType[] annotatedTypes = clStudent.getAnnotatedInterfaces();
//		AnnotatedType annotatedType;
//		for (int i = 0; i < annotatedTypes.length; i++) {
//			annotatedType = annotatedTypes[i];
//			System.out.println(
//				"AnnotatedType - " + (i + 1) + " : " + annotatedType.getType().getTypeName());
//			//获取注解
//			Annotation[] ans = annotatedType.getAnnotations();
//			System.out.println("ans.length: " + ans.length);
//			for (Annotation annotation : ans) {
//				System.out.println(annotation);
//			}
//		}
//		// 返回AnnotatedType对象，该对象表示使用类型来指定此Class对象所表示的实体的超类
//		AnnotatedType atSupper = clStudent.getAnnotatedSuperclass();
//		System.out.println(atSupper.getType().toString());
//		System.out.println(Arrays.toString(atSupper.getAnnotations()));
	
	}
	
	/**
	 * getEnumConstants()-> 返回此枚举类的元素列表，如果此Class对象不表示枚举类型，则返回null
	 */
	private static void checkEnumClass(Class clz) {
		Object[] types = clz.getEnumConstants();
		if (null != types) {
			System.out.println(clz.getName() + "是Enum");
			System.out.println(clz.getName() + "的元素列表:" + Arrays.toString(types));
		} else {
			System.out.println(clz.getName() + "不是Enum");
		}
	}
	
}
